package model.camera;

import java.io.IOException;
import java.util.List;

import model.OnScreen;
import android.annotation.SuppressLint;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.util.Log;
import android.view.SurfaceHolder;

public class CameraManager 
{
	private static final String TAG = CameraManager.class.getSimpleName();

	//Callbackbuffer
	private static int BUFFER_AMOUNT = 1;
	private static int BUFFERSIZE;
	
	private boolean flashOn;
	
	//Fields
	private static Camera _camera;
	
	//Constructor
	public CameraManager()
	{
		initializeCamera();
		setPreviewCallback(new CameraPreviewCallback());
	}
	
	public Camera getCamera()
	{
		return _camera;
	}
	
	//Helper Methods
	@SuppressLint("InlinedApi")
	private void initializeCamera()
	{
		_camera = null;
		try
		{
			_camera = Camera.open();
			Log.v(TAG,"Camera Opened");
			
			Parameters params = _camera.getParameters();
			
			//Continuous picture is more aggressive than continuous video
			params.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); 
			
			//Set fps range for camera. List is sorted high to low
			List<int[]> fpsRanges = params.getSupportedPreviewFpsRange();
			int[] minimumFps = fpsRanges.get(fpsRanges.size()-1);	//Lowest value for performance purposes
			params.setPreviewFpsRange(minimumFps[0], minimumFps[1]);
			
			//Set resolution for camera. List is sorted high to low
			List<Size> previewRanges = params.getSupportedPreviewSizes();
			Size maxRes = previewRanges.get(0);	//Highest value for better recognition of codes.
			params.setPreviewSize(maxRes.width, maxRes.height);
			
			//communicate with OnScreen
			OnScreen.getInstance().setPreviewDimension(maxRes.width, maxRes.height);
			
			//Calculate bytes needed per screen and set it as buffer
			int previewFormat = params.getPreviewFormat();
			double bitsPerPixelPreview = ImageFormat.getBitsPerPixel(previewFormat) / 8d;
			BUFFERSIZE = (int) (maxRes.width * maxRes.height * bitsPerPixelPreview);
			
			for (int i = 0; i < BUFFER_AMOUNT; i++) 
				_camera.addCallbackBuffer(new byte[BUFFERSIZE]);
			
			//Apply changes
			_camera.setParameters(params);
			
			Log.v(TAG,"Camera Parameters set");
		}
		catch(Exception e)
		{
			Log.e(TAG, String.format("Error initializing camera: %s", e.toString()));
		}
	}
	
	/**
	 * Starts capturing & drawing preview frames to the screen
	 * @param holder The SurfaceHolder for displaying the camera view
	 */
	public synchronized void startPreview(SurfaceHolder holder)
	{
		Log.d(TAG, String.format("startPreview()"));
		if(hasCamera())
		{
			try 
			{
				_camera.setPreviewDisplay(holder);
				_camera.startPreview();
			} catch (IOException e) 
			{
				Log.e(TAG, String.format("Error starting preview - %s", e.toString()));
			}
		}
	}
	
	/**
	 * Stops capturing & drawing preview frames to the screen
	 */
	public synchronized void stopPreview()
	{
		if(hasCamera())
			_camera.stopPreview();
	}
	
	public synchronized boolean hasCamera()
	{
		return _camera != null;
	}
	
	/**
	 * Stop all camera action
	 */
	public synchronized void release()
	{
		if(hasCamera())
		{
			_camera.stopPreview();
			_camera.setPreviewCallback(null);
			_camera.release();
			_camera = null;
		}
	}
	
	/**
	 * set callback for the camera, allowing the use of CameraPreviewCallback.onPreviewFrame
	 * @param callback to set. Can be null. 
	 */
	private synchronized void setPreviewCallback(CameraPreviewCallback callback)
	{
		if(hasCamera())
		{
			//With buffer to prevent memoryleak
			_camera.setPreviewCallbackWithBuffer(callback);
		}		
		Log.v(TAG, String.format("RequestNextFrame() -- %s", callback.toString()));
	}
	
	/**
	 * Adds a callback buffer to allow a new frame to be processed.
	 */
	public static void addCallbackBuffer()
	{
		if(_camera != null)
		{
			_camera.addCallbackBuffer(new byte[BUFFERSIZE]);			
		}
	}

	/**
	 * Toggle flash light.
	 */
	public void toggleFlashLight() 
	{
		Parameters params = _camera.getParameters();
		
		if(flashOn)
			params.setFlashMode(Parameters.FLASH_MODE_OFF);
		else params.setFlashMode(Parameters.FLASH_MODE_TORCH);
		
		_camera.setParameters(params);
		
		flashOn = !flashOn;
	}
}